package java_thinking.unit_21._21_7;

import net.jcip.annotations.GuardedBy;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Desc:   7-15 向 LogWriter 添加可靠的取消操作
 * @author: cww
 * @DateTime: 2019-06-16 15:15
 */

public class LogService {
    private final BlockingQueue<String> queue;
    private final LoggerThread loggerThread;
    private final PrintWriter writer;
    @GuardedBy("this") private boolean isShutdown;
    @GuardedBy("this") private int reservations;

    public LogService(Writer writer) {
        this.queue = new LinkedBlockingQueue<String>();
        this.loggerThread = new LoggerThread();
        this.writer = new PrintWriter(writer);
    }
    public void start() {
        loggerThread.start();
    }
    public void stop() {
        synchronized (this) {
            isShutdown = true;
            loggerThread.interrupt();
        }
    }

    public void log(String msg) throws InterruptedException {
        synchronized (this) {
            if (isShutdown) {
                throw new IllegalStateException("线程正准备shutDown，你塞了我也不打");
            } else {
                ++reservations;
            }
        }
        queue.put(msg);
    }

    private class LoggerThread extends Thread {
        @Override
        public void run() {
            try {
                while (true) {
                    try {
                        synchronized (LoggerThread.this) {
                            /**
                             * 如果正准备停止线程，并且当前队列中的消息数为0，则直接跳过
                             * 这里为了解决可靠关闭时的竟态条件问题，但是我们不希望加入队列时去持有一个锁，
                             * 因为 put 方法本身就可以阻塞：这里的解决办法是通过原子方法来检查关闭请求，
                             * 并且有条件的递增一个计数器来保持提交消息的权利
                             */
                            if (isShutdown && reservations == 0)
                                //满足条件则跳出 while
                                break;
                        }
                        //未满足条件则继续把需要消费的消费完，避免关闭线程后还有一些已经进入队列的没消费完
                        String msg = queue.take();
                        synchronized (LoggerThread.this) {
                            --reservations;
                        }
                        writer.println(msg);
                    } catch (InterruptedException e) {
                        /* retry */
                    }
                }
            } finally {
                writer.close();
            }
        }
    }
}
